package edu.fiu.cis.personalize.server;

import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import edu.fiu.cis.personalize.server.berkDB.NodeStoreSerializer;
import edu.fiu.cis.personalize.server.graph.Document;
import edu.fiu.cis.personalize.server.graph.Group;
import edu.fiu.cis.personalize.server.graph.Node;
import edu.fiu.cis.personalize.server.graph.NodeType;
import edu.fiu.cis.personalize.server.graph.Query;
import edu.fiu.cis.personalize.server.graph.User;
import edu.fiu.cis.personalize.server.graph.store.Edges;
import edu.fiu.cis.personalize.server.graph.store.NodeCache;
import edu.fiu.cis.personalize.server.graph.store.NodeStore;
import edu.fiu.cis.personalize.server.graph.update.GraphUpdateFactory;
import edu.fiu.cis.personalize.server.util.PajekFileGenerator;

public final class GraphMediator {

	private static boolean inUpdate;

	public static boolean CLEAR_DB_ON_LOAD = true;

	private static NodeStore _store = NodeStore.create();

	public static NodeStore get() {
		if (inUpdate)
			synchronized (semaphore) {
				return _store;
			}
		else {
			return _store;
		}
	}

	public static void load() {
		Map<String, Integer> keyMap = NodeStoreSerializer.loadKeys();

		// loading users
		int maxUserId = keyMap.get("u");
		load(NodeType.USER, maxUserId, User.class);

		// loading documents
		int maxDocId = keyMap.get("d");
		load(NodeType.DOCUMENT, maxDocId, Document.class);

		// loading queries
		int maxQueryId = keyMap.get("q");
		load(NodeType.QUERY, maxQueryId, Query.class);

		// load groups
		int maxGroupId = keyMap.get("g");
		load(NodeType.GROUP, maxGroupId, Group.class);

	}

	private static Object semaphore = new Object();

	public static void persist(Set<UpdateElement> updateList) {
		synchronized (semaphore) {
			int u = _store.getCounterValue(NodeType.USER);
			int q = _store.getCounterValue(NodeType.QUERY);
			int d = _store.getCounterValue(NodeType.DOCUMENT);
			int g = _store.getCounterValue(NodeType.GROUP);

			NodeStoreSerializer.storeKeys(u, g, q, d);

			for (UpdateElement e : updateList) {
				Node n = _store.get(e.getId(), e.getType());
				NodeStoreSerializer.store(n);
			}

		}
	}

	public static boolean isInUpdate() {
		return inUpdate;
	}

	public static void setInUpdate(boolean inUpdate) {
		GraphMediator.inUpdate = inUpdate;
	}

	@SuppressWarnings("unchecked")
	private static <E extends Node> void load(NodeType type, int max,
			Class<E> clazz) {
		// load groups
		_store.loadKey(type, max);
		for (int i = 1; i <= max; i++) {
			E e = (E) NodeStoreSerializer.load(type, i);
			if (e != null) {
				_store.load(e);
			}
		}
	}

	public static <E extends Node, F extends Node> void addEdge(E from, E to) {
		GraphUpdateFactory.update(from, to);
	}

	@SuppressWarnings("unchecked")
	public static <E extends Node> E getNode(String idStr, NodeType type) {
		return (E) get().get(idStr, type, true);
	}

	public static edu.fiu.cis.personalize.shared.User registerUser(
			String access_token) {
		System.out.println("registering user..: " + access_token);
		FaceBookAPI fbAccess = new FaceBookAPI(access_token);

		String userName = fbAccess.fqlUserName();

		User u = (User) GraphMediator.get().get(userName, NodeType.USER, true);

		for (com.restfb.types.Group fg : fbAccess.fqlGroups()) {
			Group g = (Group) GraphMediator.get().get(fg.getName(),
					NodeType.GROUP, true);
			g.setDescription(g.getDescription());
			g.addMember(u);
			addEdge(g, u);
			addEdge(u, g);

			if (g.isNew()) {
				addTEdges(g, get().getAllGroups());
				addTEdges(g, get().getAllQueries());
			}

		}

		return new edu.fiu.cis.personalize.shared.User(u.getId(),
				u.getUserName());

	}

	public static void documentClicked(int userId, int documentId, int queryId) {
		User u = (User) GraphMediator.get().get(userId, NodeType.USER);
		Document d = (Document) GraphMediator.get().get(documentId,
				NodeType.DOCUMENT);
		Query q = (Query) GraphMediator.get().get(queryId, NodeType.QUERY);
		u.clicksDocument(d);
		d.clickedBy(u, q);

		addEdge(u, d);
		addEdge(d, u);

		addEdge(q, d);
		addEdge(d, q);

	}

	public static synchronized String print() {
		PajekFileGenerator gen = new PajekFileGenerator();

		// add all nodes
		for (Group g : get().getAllGroups()) {
			gen.addVertex(g.getStringKey(), g.getRank(), "Red");
		}

		for (User u : get().getAllUsers()) {
			gen.addVertex(u.getStringKey(), u.getRank(), "Yellow");
		}

		for (Query q : get().getAllQueries()) {
			gen.addVertex(q.getStringKey(), q.getRank(), "Blue");
		}

		for (Document d : get().getAllDocuments()) {
			gen.addVertex(d.getStringKey(), d.getRank(), "Black");
		}
		// add all edges
		processEdges(gen, get().getAllGroups());
		processEdges(gen, get().getAllUsers());
		processEdges(gen, get().getAllDocuments());
		processEdges(gen, get().getAllQueries());

		return gen.getResult();

	}

	private static <E extends Node> void processEdges(PajekFileGenerator gen,
			Iterable<E> iter) {
		for (E g : iter) {
			writeEdges(gen, g, User.class, NodeType.USER);
			writeEdges(gen, g, Document.class, NodeType.DOCUMENT);
			writeEdges(gen, g, Query.class, NodeType.QUERY);
			writeEdges(gen, g, Group.class, NodeType.GROUP);
		}
	}

	public static Query querySubmitted(String query, int uid) {
		Query q = (Query) GraphMediator.get().get(query, NodeType.QUERY, true);
		User u = (User) get().get(uid, NodeType.USER);
		u.submitsQuery(q);
		q.submittedByUser(u);
		addEdge(q, u);
		addEdge(u, q);

		if (q.isNew()) {
			addTEdges(q, get().getAllQueries());
			addTEdges(q, get().getAllGroups());
		}

		return q;
	}

	public static void performDocumentQueryAssociation(Query q, Document d) {
		// addEdge(d, q);
		// addEdge(q, d);
		q.displaysDocument(d);
		d.displayedAfterQuery(q);

		addEdge(q, d);
		addEdge(d, q);
	}

	private static <E extends Node> void addTEdges(Node src, NodeCache<E> nodes) {
		for (Node n : nodes) {
			GraphUpdateFactory.update(src, n);
		}
		src.setNew(false);
	}

	@SuppressWarnings("unchecked")
	private static <E extends Node> void writeEdges(PajekFileGenerator gen,
			Node s, Class<E> clazz, NodeType type) {
		Edges<E> edges = (Edges<E>) s.getEdges().getEdgeMap(type);
		for (Entry<Integer, Double> e : edges.getInLinksMap().entrySet()) {
			Node n = get().get(e.getKey(), type);
			gen.addArc(n.getStringKey(), s.getStringKey(), e.getValue());
		}

		for (Entry<Integer, Double> e : edges.getOutLinksMap().entrySet()) {
			Node n = get().get(e.getKey(), type);
			gen.addArc(s.getStringKey(), n.getStringKey(), e.getValue());
		}
	}

	public static Node getNode(int id, NodeType type) {
		return get().get(id, type);
	}

}
